home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-gdata / atom / service.py < prev    next >
Encoding:
Python Source  |  2009-02-11  |  27.7 KB  |  726 lines

  1. #
  2. # Copyright (C) 2006, 2007, 2008 Google Inc.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. #      http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15.  
  16.  
  17. """AtomService provides CRUD ops. in line with the Atom Publishing Protocol.
  18.  
  19.   AtomService: Encapsulates the ability to perform insert, update and delete
  20.                operations with the Atom Publishing Protocol on which GData is
  21.                based. An instance can perform query, insertion, deletion, and
  22.                update.
  23.  
  24.   HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request
  25.        to the specified end point. An AtomService object or a subclass can be
  26.        used to specify information about the request.
  27. """
  28.  
  29. __author__ = 'api.jscudder (Jeff Scudder)'
  30.  
  31.  
  32. import atom.http_interface
  33. import atom.url
  34. import atom.http
  35. import atom.token_store
  36.  
  37. import os
  38. import httplib
  39. import urllib
  40. import re
  41. import base64
  42. import socket
  43. import warnings
  44. try:
  45.   from xml.etree import cElementTree as ElementTree
  46. except ImportError:
  47.   try:
  48.     import cElementTree as ElementTree
  49.   except ImportError:
  50.     try:
  51.       from xml.etree import ElementTree
  52.     except ImportError:
  53.       from elementtree import ElementTree
  54.  
  55.  
  56. class AtomService(object):
  57.   """Performs Atom Publishing Protocol CRUD operations.
  58.   
  59.   The AtomService contains methods to perform HTTP CRUD operations. 
  60.   """
  61.  
  62.   # Default values for members
  63.   port = 80
  64.   ssl = False
  65.   # Set the current_token to force the AtomService to use this token
  66.   # instead of searching for an appropriate token in the token_store.
  67.   current_token = None
  68.   auto_store_tokens = True
  69.   auto_set_current_token = True
  70.  
  71.   def _get_override_token(self):
  72.     return self.current_token
  73.  
  74.   def _set_override_token(self, token):
  75.     self.current_token = token
  76.  
  77.   override_token = property(_get_override_token, _set_override_token)
  78.  
  79.   def __init__(self, server=None, additional_headers=None, 
  80.       application_name='', http_client=None, token_store=None):
  81.     """Creates a new AtomService client.
  82.     
  83.     Args:
  84.       server: string (optional) The start of a URL for the server
  85.               to which all operations should be directed. Example: 
  86.               'www.google.com'
  87.       additional_headers: dict (optional) Any additional HTTP headers which
  88.                           should be included with CRUD operations.
  89.       http_client: An object responsible for making HTTP requests using a
  90.                    request method. If none is provided, a new instance of
  91.                    atom.http.ProxiedHttpClient will be used.
  92.       token_store: Keeps a collection of authorization tokens which can be
  93.                    applied to requests for a specific URLs. Critical methods are
  94.                    find_token based on a URL (atom.url.Url or a string), add_token,
  95.                    and remove_token.
  96.     """
  97.     self.http_client = http_client or atom.http.ProxiedHttpClient()
  98.     self.token_store = token_store or atom.token_store.TokenStore()
  99.     self.server = server
  100.     self.additional_headers = additional_headers or {}
  101.     self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
  102.         application_name,)
  103.     # If debug is True, the HTTPConnection will display debug information
  104.     self._set_debug(False)
  105.  
  106.   def _get_debug(self):
  107.     return self.http_client.debug
  108.  
  109.   def _set_debug(self, value):
  110.     self.http_client.debug = value
  111.  
  112.   debug = property(_get_debug, _set_debug, 
  113.       doc='If True, HTTP debug information is printed.')
  114.  
  115.   def use_basic_auth(self, username, password, scopes=None):
  116.     if username is not None and password is not None:
  117.       if scopes is None:
  118.         scopes = [atom.token_store.SCOPE_ALL]
  119.       base_64_string = base64.encodestring('%s:%s' % (username, password))
  120.       token = BasicAuthToken('Basic %s' % base_64_string.strip(), 
  121.           scopes=[atom.token_store.SCOPE_ALL])
  122.       if self.auto_set_current_token:
  123.         self.current_token = token
  124.       if self.auto_store_tokens:
  125.         return self.token_store.add_token(token)
  126.       return True
  127.     return False
  128.  
  129.   def UseBasicAuth(self, username, password, for_proxy=False):
  130.     """Sets an Authenticaiton: Basic HTTP header containing plaintext.
  131.  
  132.     Deprecated, use use_basic_auth instead.
  133.     
  134.     The username and password are base64 encoded and added to an HTTP header
  135.     which will be included in each request. Note that your username and 
  136.     password are sent in plaintext.
  137.  
  138.     Args:
  139.       username: str
  140.       password: str
  141.     """
  142.     self.use_basic_auth(username, password)
  143.  
  144.   def request(self, operation, url, data=None, headers=None, 
  145.       url_params=None):
  146.     if isinstance(url, str):
  147.       if not url.startswith('http') and self.ssl:
  148.         url = atom.url.parse_url('https://%s%s' % (self.server, url))
  149.       elif not url.startswith('http'):
  150.         url = atom.url.parse_url('http://%s%s' % (self.server, url))
  151.       else:
  152.         url = atom.url.parse_url(url)
  153.  
  154.     if url_params:
  155.       for name, value in url_params.iteritems():
  156.         url.params[name] = value
  157.  
  158.     all_headers = self.additional_headers.copy()
  159.     if headers:
  160.       all_headers.update(headers)
  161.  
  162.     # If the list of headers does not include a Content-Length, attempt to
  163.     # calculate it based on the data object.
  164.     if data and 'Content-Length' not in all_headers:
  165.       content_length = CalculateDataLength(data)
  166.       if content_length:
  167.         all_headers['Content-Length'] = str(content_length)
  168.  
  169.     # Find an Authorization token for this URL if one is available.
  170.     if self.override_token:
  171.       auth_token = self.override_token
  172.     else:
  173.       auth_token = self.token_store.find_token(url)
  174.     return auth_token.perform_request(self.http_client, operation, url, 
  175.         data=data, headers=all_headers)
  176.  
  177.   # CRUD operations
  178.   def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
  179.     """Query the APP server with the given URI
  180.  
  181.     The uri is the portion of the URI after the server value 
  182.     (server example: 'www.google.com').
  183.  
  184.     Example use:
  185.     To perform a query against Google Base, set the server to 
  186.     'base.google.com' and set the uri to '/base/feeds/...', where ... is 
  187.     your query. For example, to find snippets for all digital cameras uri 
  188.     should be set to: '/base/feeds/snippets?bq=digital+camera'
  189.  
  190.     Args:
  191.       uri: string The query in the form of a URI. Example:
  192.            '/base/feeds/snippets?bq=digital+camera'.
  193.       extra_headers: dicty (optional) Extra HTTP headers to be included
  194.                      in the GET request. These headers are in addition to 
  195.                      those stored in the client's additional_headers property.
  196.                      The client automatically sets the Content-Type and 
  197.                      Authorization headers.
  198.       url_params: dict (optional) Additional URL parameters to be included
  199.                   in the query. These are translated into query arguments
  200.                   in the form '&dict_key=value&...'.
  201.                   Example: {'max-results': '250'} becomes &max-results=250
  202.       escape_params: boolean (optional) If false, the calling code has already
  203.                      ensured that the query will form a valid URL (all
  204.                      reserved characters have been escaped). If true, this
  205.                      method will escape the query and any URL parameters
  206.                      provided.
  207.  
  208.     Returns:
  209.       httplib.HTTPResponse The server's response to the GET request.
  210.     """
  211.     return self.request('GET', uri, data=None, headers=extra_headers, 
  212.                         url_params=url_params)
  213.  
  214.   def Post(self, data, uri, extra_headers=None, url_params=None, 
  215.            escape_params=True, content_type='application/atom+xml'):
  216.     """Insert data into an APP server at the given URI.
  217.  
  218.     Args:
  219.       data: string, ElementTree._Element, or something with a __str__ method 
  220.             The XML to be sent to the uri. 
  221.       uri: string The location (feed) to which the data should be inserted. 
  222.            Example: '/base/feeds/items'. 
  223.       extra_headers: dict (optional) HTTP headers which are to be included. 
  224.                      The client automatically sets the Content-Type,
  225.                      Authorization, and Content-Length headers.
  226.       url_params: dict (optional) Additional URL parameters to be included
  227.                   in the URI. These are translated into query arguments
  228.                   in the form '&dict_key=value&...'.
  229.                   Example: {'max-results': '250'} becomes &max-results=250
  230.       escape_params: boolean (optional) If false, the calling code has already
  231.                      ensured that the query will form a valid URL (all
  232.                      reserved characters have been escaped). If true, this
  233.                      method will escape the query and any URL parameters
  234.                      provided.
  235.  
  236.     Returns:
  237.       httplib.HTTPResponse Server's response to the POST request.
  238.     """
  239.     if extra_headers is None:
  240.       extra_headers = {}
  241.     if content_type:
  242.       extra_headers['Content-Type'] = content_type
  243.     return self.request('POST', uri, data=data, headers=extra_headers, 
  244.                         url_params=url_params)
  245.  
  246.   def Put(self, data, uri, extra_headers=None, url_params=None, 
  247.            escape_params=True, content_type='application/atom+xml'):
  248.     """Updates an entry at the given URI.
  249.      
  250.     Args:
  251.       data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The 
  252.             XML containing the updated data.
  253.       uri: string A URI indicating entry to which the update will be applied.
  254.            Example: '/base/feeds/items/ITEM-ID'
  255.       extra_headers: dict (optional) HTTP headers which are to be included.
  256.                      The client automatically sets the Content-Type,
  257.                      Authorization, and Content-Length headers.
  258.       url_params: dict (optional) Additional URL parameters to be included
  259.                   in the URI. These are translated into query arguments
  260.                   in the form '&dict_key=value&...'.
  261.                   Example: {'max-results': '250'} becomes &max-results=250
  262.       escape_params: boolean (optional) If false, the calling code has already
  263.                      ensured that the query will form a valid URL (all
  264.                      reserved characters have been escaped). If true, this
  265.                      method will escape the query and any URL parameters
  266.                      provided.
  267.   
  268.     Returns:
  269.       httplib.HTTPResponse Server's response to the PUT request.
  270.     """
  271.     if extra_headers is None:
  272.       extra_headers = {}
  273.     if content_type:
  274.       extra_headers['Content-Type'] = content_type
  275.     return self.request('PUT', uri, data=data, headers=extra_headers, 
  276.                         url_params=url_params)
  277.  
  278.   def Delete(self, uri, extra_headers=None, url_params=None, 
  279.              escape_params=True):
  280.     """Deletes the entry at the given URI.
  281.  
  282.     Args:
  283.       uri: string The URI of the entry to be deleted. Example: 
  284.            '/base/feeds/items/ITEM-ID'
  285.       extra_headers: dict (optional) HTTP headers which are to be included.
  286.                      The client automatically sets the Content-Type and
  287.                      Authorization headers.
  288.       url_params: dict (optional) Additional URL parameters to be included
  289.                   in the URI. These are translated into query arguments
  290.                   in the form '&dict_key=value&...'.
  291.                   Example: {'max-results': '250'} becomes &max-results=250
  292.       escape_params: boolean (optional) If false, the calling code has already
  293.                      ensured that the query will form a valid URL (all
  294.                      reserved characters have been escaped). If true, this
  295.                      method will escape the query and any URL parameters
  296.                      provided.
  297.  
  298.     Returns:
  299.       httplib.HTTPResponse Server's response to the DELETE request.
  300.     """
  301.     return self.request('DELETE', uri, data=None, headers=extra_headers, 
  302.                         url_params=url_params)
  303.  
  304.  
  305. class BasicAuthToken(atom.http_interface.GenericToken):
  306.   def __init__(self, auth_header, scopes=None):
  307.     """Creates a token used to add Basic Auth headers to HTTP requests.
  308.  
  309.     Args:
  310.       auth_header: str The value for the Authorization header.
  311.       scopes: list of str or atom.url.Url specifying the beginnings of URLs
  312.           for which this token can be used. For example, if scopes contains
  313.           'http://example.com/foo', then this token can be used for a request to
  314.           'http://example.com/foo/bar' but it cannot be used for a request to
  315.           'http://example.com/baz'
  316.     """
  317.     self.auth_header = auth_header
  318.     self.scopes = scopes or []
  319.  
  320.   def perform_request(self, http_client, operation, url, data=None,
  321.                       headers=None):
  322.     """Sets the Authorization header to the basic auth string."""
  323.     if headers is None:
  324.       headers = {'Authorization':self.auth_header}
  325.     else:
  326.       headers['Authorization'] = self.auth_header
  327.     return http_client.request(operation, url, data=data, headers=headers)
  328.  
  329.   def __str__(self):
  330.     return self.auth_header
  331.  
  332.   def valid_for_scope(self, url):
  333.     """Tells the caller if the token authorizes access to the desired URL.
  334.     """
  335.     if isinstance(url, (str, unicode)):
  336.       url = atom.url.parse_url(url)
  337.     for scope in self.scopes:
  338.       if scope == atom.token_store.SCOPE_ALL:
  339.         return True
  340.       if isinstance(scope, (str, unicode)):
  341.         scope = atom.url.parse_url(scope)
  342.       if scope == url:
  343.         return True
  344.       # Check the host and the path, but ignore the port and protocol.
  345.       elif scope.host == url.host and not scope.path:
  346.         return True
  347.       elif scope.host == url.host and scope.path and not url.path:
  348.         continue
  349.       elif scope.host == url.host and url.path.startswith(scope.path):
  350.         return True
  351.     return False
  352.  
  353.  
  354. def PrepareConnection(service, full_uri):
  355.   """Opens a connection to the server based on the full URI.
  356.  
  357.   This method is deprecated, instead use atom.http.HttpClient.request.
  358.  
  359.   Examines the target URI and the proxy settings, which are set as
  360.   environment variables, to open a connection with the server. This
  361.   connection is used to make an HTTP request.
  362.  
  363.   Args:
  364.     service: atom.AtomService or a subclass. It must have a server string which
  365.       represents the server host to which the request should be made. It may also
  366.       have a dictionary of additional_headers to send in the HTTP request.
  367.     full_uri: str Which is the target relative (lacks protocol and host) or
  368.     absolute URL to be opened. Example:
  369.     'https://www.google.com/accounts/ClientLogin' or
  370.     'base/feeds/snippets' where the server is set to www.google.com.
  371.  
  372.   Returns:
  373.     A tuple containing the httplib.HTTPConnection and the full_uri for the
  374.     request.
  375.   """
  376.   deprecation('calling deprecated function PrepareConnection')
  377.   (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
  378.   if ssl:
  379.     # destination is https
  380.     proxy = os.environ.get('https_proxy')
  381.     if proxy:
  382.       (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True)
  383.       proxy_username = os.environ.get('proxy-username')
  384.       if not proxy_username:
  385.         proxy_username = os.environ.get('proxy_username')
  386.       proxy_password = os.environ.get('proxy-password')
  387.       if not proxy_password:
  388.         proxy_password = os.environ.get('proxy_password')
  389.       if proxy_username:
  390.         user_auth = base64.encodestring('%s:%s' % (proxy_username,
  391.                                                    proxy_password))
  392.         proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % (
  393.             user_auth.strip()))
  394.       else:
  395.         proxy_authorization = ''
  396.       proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port)
  397.       user_agent = 'User-Agent: %s\r\n' % (
  398.           service.additional_headers['User-Agent'])
  399.       proxy_pieces = (proxy_connect + proxy_authorization + user_agent
  400.                        + '\r\n')
  401.  
  402.       #now connect, very simple recv and error checking
  403.       p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  404.       p_sock.connect((p_server,p_port))
  405.       p_sock.sendall(proxy_pieces)
  406.       response = ''
  407.  
  408.       # Wait for the full response.
  409.       while response.find("\r\n\r\n") == -1:
  410.         response += p_sock.recv(8192)
  411.        
  412.       p_status=response.split()[1]
  413.       if p_status!=str(200):
  414.         raise 'Error status=',str(p_status)
  415.  
  416.       # Trivial setup for ssl socket.
  417.       ssl = socket.ssl(p_sock, None, None)
  418.       fake_sock = httplib.FakeSocket(p_sock, ssl)
  419.  
  420.       # Initalize httplib and replace with the proxy socket.
  421.       connection = httplib.HTTPConnection(server)
  422.       connection.sock=fake_sock
  423.       full_uri = partial_uri
  424.  
  425.     else:
  426.       connection = httplib.HTTPSConnection(server, port)
  427.       full_uri = partial_uri
  428.  
  429.   else:
  430.     # destination is http
  431.     proxy = os.environ.get('http_proxy')
  432.     if proxy:
  433.       (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True)
  434.       proxy_username = os.environ.get('proxy-username')
  435.       if not proxy_username:
  436.         proxy_username = os.environ.get('proxy_username')
  437.       proxy_password = os.environ.get('proxy-password')
  438.       if not proxy_password:
  439.         proxy_password = os.environ.get('proxy_password')
  440.       if proxy_username:
  441.         UseBasicAuth(service, proxy_username, proxy_password, True)
  442.       connection = httplib.HTTPConnection(p_server, p_port)
  443.       if not full_uri.startswith("http://"):
  444.         if full_uri.startswith("/"):
  445.           full_uri = "http://%s%s" % (service.server, full_uri)
  446.         else:
  447.           full_uri = "http://%s/%s" % (service.server, full_uri)
  448.     else:
  449.       connection = httplib.HTTPConnection(server, port)
  450.       full_uri = partial_uri
  451.  
  452.   return (connection, full_uri)
  453.  
  454.  
  455. def UseBasicAuth(service, username, password, for_proxy=False):
  456.   """Sets an Authenticaiton: Basic HTTP header containing plaintext.
  457.  
  458.   Deprecated, use AtomService.use_basic_auth insread.
  459.   
  460.   The username and password are base64 encoded and added to an HTTP header
  461.   which will be included in each request. Note that your username and 
  462.   password are sent in plaintext. The auth header is added to the 
  463.   additional_headers dictionary in the service object.
  464.  
  465.   Args:
  466.     service: atom.AtomService or a subclass which has an 
  467.         additional_headers dict as a member.
  468.     username: str
  469.     password: str
  470.   """
  471.   deprecation('calling deprecated function UseBasicAuth')
  472.   base_64_string = base64.encodestring('%s:%s' % (username, password))
  473.   base_64_string = base_64_string.strip()
  474.   if for_proxy:
  475.     header_name = 'Proxy-Authorization'
  476.   else:
  477.     header_name = 'Authorization'
  478.   service.additional_headers[header_name] = 'Basic %s' % (base_64_string,)
  479.  
  480.  
  481. def ProcessUrl(service, url, for_proxy=False):
  482.   """Processes a passed URL.  If the URL does not begin with https?, then
  483.   the default value for server is used
  484.  
  485.   This method is deprecated, use atom.url.parse_url instead.
  486.   """
  487.   if not isinstance(url, atom.url.Url):
  488.     url = atom.url.parse_url(url)
  489.  
  490.   server = url.host
  491.   ssl = False
  492.   port = 80
  493.  
  494.   if not server:
  495.     if hasattr(service, 'server'):
  496.       server = service.server
  497.     else:
  498.       server = service
  499.     if not url.protocol and hasattr(service, 'ssl'):
  500.       ssl = service.ssl
  501.     if hasattr(service, 'port'):
  502.       port = service.port
  503.   else:
  504.     if url.protocol == 'https':
  505.       ssl = True
  506.     elif url.protocol == 'http':
  507.       ssl = False
  508.     if url.port:
  509.       port = int(url.port)
  510.     elif port == 80 and ssl:
  511.       port = 443
  512.  
  513.   return (server, port, ssl, url.get_request_uri())
  514.  
  515. def DictionaryToParamList(url_parameters, escape_params=True):
  516.   """Convert a dictionary of URL arguments into a URL parameter string.
  517.  
  518.   This function is deprcated, use atom.url.Url instead.
  519.  
  520.   Args:
  521.     url_parameters: The dictionaty of key-value pairs which will be converted
  522.                     into URL parameters. For example,
  523.                     {'dry-run': 'true', 'foo': 'bar'}
  524.                     will become ['dry-run=true', 'foo=bar'].
  525.  
  526.   Returns:
  527.     A list which contains a string for each key-value pair. The strings are
  528.     ready to be incorporated into a URL by using '&'.join([] + parameter_list)
  529.   """
  530.   # Choose which function to use when modifying the query and parameters.
  531.   # Use quote_plus when escape_params is true.
  532.   transform_op = [str, urllib.quote_plus][bool(escape_params)]
  533.   # Create a list of tuples containing the escaped version of the
  534.   # parameter-value pairs.
  535.   parameter_tuples = [(transform_op(param), transform_op(value))
  536.                      for param, value in (url_parameters or {}).items()]
  537.   # Turn parameter-value tuples into a list of strings in the form
  538.   # 'PARAMETER=VALUE'.
  539.   return ['='.join(x) for x in parameter_tuples]
  540.  
  541.  
  542. def BuildUri(uri, url_params=None, escape_params=True):
  543.   """Converts a uri string and a collection of parameters into a URI.
  544.  
  545.   This function is deprcated, use atom.url.Url instead.
  546.  
  547.   Args:
  548.     uri: string
  549.     url_params: dict (optional)
  550.     escape_params: boolean (optional)
  551.     uri: string The start of the desired URI. This string can alrady contain
  552.          URL parameters. Examples: '/base/feeds/snippets', 
  553.          '/base/feeds/snippets?bq=digital+camera'
  554.     url_parameters: dict (optional) Additional URL parameters to be included
  555.                     in the query. These are translated into query arguments
  556.                     in the form '&dict_key=value&...'.
  557.                     Example: {'max-results': '250'} becomes &max-results=250
  558.     escape_params: boolean (optional) If false, the calling code has already
  559.                    ensured that the query will form a valid URL (all
  560.                    reserved characters have been escaped). If true, this
  561.                    method will escape the query and any URL parameters
  562.                    provided.
  563.  
  564.   Returns:
  565.     string The URI consisting of the escaped URL parameters appended to the
  566.     initial uri string.
  567.   """
  568.   # Prepare URL parameters for inclusion into the GET request.
  569.   parameter_list = DictionaryToParamList(url_params, escape_params)
  570.  
  571.   # Append the URL parameters to the URL.
  572.   if parameter_list:
  573.     if uri.find('?') != -1:
  574.       # If there are already URL parameters in the uri string, add the
  575.       # parameters after a new & character.
  576.       full_uri = '&'.join([uri] + parameter_list)
  577.     else:
  578.       # The uri string did not have any URL parameters (no ? character)
  579.       # so put a ? between the uri and URL parameters.
  580.       full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list)))  
  581.   else:
  582.     full_uri = uri
  583.         
  584.   return full_uri
  585.  
  586.   
  587. def HttpRequest(service, operation, data, uri, extra_headers=None, 
  588.     url_params=None, escape_params=True, content_type='application/atom+xml'):
  589.   """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
  590.   
  591.   This method is deprecated, use atom.http.HttpClient.request instead.
  592.  
  593.   Usage example, perform and HTTP GET on http://www.google.com/:
  594.     import atom.service
  595.     client = atom.service.AtomService()
  596.     http_response = client.Get('http://www.google.com/')
  597.   or you could set the client.server to 'www.google.com' and use the 
  598.   following:
  599.     client.server = 'www.google.com'
  600.     http_response = client.Get('/')
  601.  
  602.   Args:
  603.     service: atom.AtomService object which contains some of the parameters 
  604.         needed to make the request. The following members are used to 
  605.         construct the HTTP call: server (str), additional_headers (dict), 
  606.         port (int), and ssl (bool).
  607.     operation: str The HTTP operation to be performed. This is usually one of
  608.         'GET', 'POST', 'PUT', or 'DELETE'
  609.     data: ElementTree, filestream, list of parts, or other object which can be 
  610.         converted to a string. 
  611.         Should be set to None when performing a GET or PUT.
  612.         If data is a file-like object which can be read, this method will read
  613.         a chunk of 100K bytes at a time and send them. 
  614.         If the data is a list of parts to be sent, each part will be evaluated
  615.         and sent.
  616.     uri: The beginning of the URL to which the request should be sent. 
  617.         Examples: '/', '/base/feeds/snippets', 
  618.         '/m8/feeds/contacts/default/base'
  619.     extra_headers: dict of strings. HTTP headers which should be sent
  620.         in the request. These headers are in addition to those stored in 
  621.         service.additional_headers.
  622.     url_params: dict of strings. Key value pairs to be added to the URL as
  623.         URL parameters. For example {'foo':'bar', 'test':'param'} will 
  624.         become ?foo=bar&test=param.
  625.     escape_params: bool default True. If true, the keys and values in 
  626.         url_params will be URL escaped when the form is constructed 
  627.         (Special characters converted to %XX form.)
  628.     content_type: str The MIME type for the data being sent. Defaults to
  629.         'application/atom+xml', this is only used if data is set.
  630.   """
  631.   deprecation('call to deprecated function HttpRequest')
  632.   full_uri = BuildUri(uri, url_params, escape_params)
  633.   (connection, full_uri) = PrepareConnection(service, full_uri)
  634.  
  635.   if extra_headers is None:
  636.     extra_headers = {}
  637.  
  638.   # Turn on debug mode if the debug member is set.
  639.   if service.debug:
  640.     connection.debuglevel = 1
  641.  
  642.   connection.putrequest(operation, full_uri)
  643.  
  644.   # If the list of headers does not include a Content-Length, attempt to 
  645.   # calculate it based on the data object.
  646.   if (data and not service.additional_headers.has_key('Content-Length') and 
  647.       not extra_headers.has_key('Content-Length')):
  648.     content_length = CalculateDataLength(data)
  649.     if content_length:
  650.       extra_headers['Content-Length'] = str(content_length)
  651.  
  652.   if content_type:
  653.     extra_headers['Content-Type'] = content_type 
  654.  
  655.   # Send the HTTP headers.
  656.   if isinstance(service.additional_headers, dict):
  657.     for header in service.additional_headers:
  658.       connection.putheader(header, service.additional_headers[header])
  659.   if isinstance(extra_headers, dict):
  660.     for header in extra_headers:
  661.       connection.putheader(header, extra_headers[header])
  662.   connection.endheaders()
  663.  
  664.   # If there is data, send it in the request.
  665.   if data:
  666.     if isinstance(data, list):
  667.       for data_part in data:
  668.         __SendDataPart(data_part, connection)
  669.     else:
  670.       __SendDataPart(data, connection)
  671.  
  672.   # Return the HTTP Response from the server.
  673.   return connection.getresponse()
  674.   
  675.  
  676. def __SendDataPart(data, connection):
  677.   """This method is deprecated, use atom.http._send_data_part"""
  678.   deprecated('call to deprecated function __SendDataPart')
  679.   if isinstance(data, str):
  680.     #TODO add handling for unicode.
  681.     connection.send(data)
  682.     return
  683.   elif ElementTree.iselement(data):
  684.     connection.send(ElementTree.tostring(data))
  685.     return
  686.   # Check to see if data is a file-like object that has a read method.
  687.   elif hasattr(data, 'read'):
  688.     # Read the file and send it a chunk at a time.
  689.     while 1:
  690.       binarydata = data.read(100000)
  691.       if binarydata == '': break
  692.       connection.send(binarydata)
  693.     return
  694.   else:
  695.     # The data object was not a file.
  696.     # Try to convert to a string and send the data.
  697.     connection.send(str(data))
  698.     return
  699.  
  700.  
  701. def CalculateDataLength(data):
  702.   """Attempts to determine the length of the data to send. 
  703.   
  704.   This method will respond with a length only if the data is a string or
  705.   and ElementTree element.
  706.  
  707.   Args:
  708.     data: object If this is not a string or ElementTree element this funtion
  709.         will return None.
  710.   """
  711.   if isinstance(data, str):
  712.     return len(data)
  713.   elif isinstance(data, list):
  714.     return None
  715.   elif ElementTree.iselement(data):
  716.     return len(ElementTree.tostring(data))
  717.   elif hasattr(data, 'read'):
  718.     # If this is a file-like object, don't try to guess the length.
  719.     return None
  720.   else:
  721.     return len(str(data))
  722.     
  723.  
  724. def deprecation(message):
  725.   warnings.warn(message, DeprecationWarning, stacklevel=2)
  726.